﻿namespace TrinityEngine
{
    public struct Vector2 : IEquatable<Vector2>
    {
        public float x;

        public float y;

        public float sqrMagnitude => x * x + y * y;

        public float magnitude => Mathf.Sqrt(sqrMagnitude);

        public Vector2 normalized
        {
            get
            {
                if (x == 0 && y == 0)
                {
                    return zero;
                }
                else
                {
                    return new Vector2(x / magnitude, y / magnitude);
                }
            }
        }

        public Vector2(float x, float y)
        {
            this.x = x;
            this.y = y;
        }

        public readonly static Vector2 zero = new Vector2(0.0f, 0.0f);

        public readonly static Vector2 one = new Vector2(1.0f, 1.0f);

        public readonly static Vector2 up = new Vector2(0.0f, 1.0f);

        public readonly static Vector2 down = new Vector2(0.0f, -1.0f);

        public readonly static Vector2 left = new Vector2(-1.0f, 0.0f);

        public readonly static Vector2 right = new Vector2(1.0f, 0.0f);

        public Vector3 ToVector3(float z = 0)
        {
            return new Vector3(x, y, z);
        }

        public static Vector2 operator -(Vector2 a)
        {
            return new Vector2(-a.x, -a.y);
        }

        public static Vector2 operator +(Vector2 a, Vector2 b)
        {
            return new Vector2(a.x + b.x, a.y + b.y);
        }

        public static Vector2 operator -(Vector2 a, Vector2 b)
        {
            return new Vector2(a.x - b.x, a.y - b.y);
        }

        public static Vector2 operator *(Vector2 a, Vector2 b)
        {
            return new Vector2(a.x * b.x, a.y * b.y);
        }

        public static Vector2 operator *(Vector2 a, float b)
        {
            return new Vector2(a.x * b, a.y * b);
        }

        public static Vector2 operator *(float a, Vector2 b)
        {
            return b * a;
        }

        public static Vector2 operator *(Vector2 a, Vector2Int b)
        {
            return new Vector2(a.x * b.x, a.y * b.y);
        }

        public static Vector2 operator *(Vector2Int a, Vector2 b)
        {
            return b * a;
        }

        public static Vector2 operator /(Vector2 a, Vector2 b)
        {
            if (b.x * b.y == 0.0f)
            {
                throw new Exception("Dividend could not be zero!");
            }

            return new Vector2(a.x / b.x, a.y / b.y);
        }

        public static Vector2 operator /(Vector2 a, float b)
        {
            if (b == 0.0f)
            {
                throw new Exception("Dividend could not be zero!");
            }

            return new Vector2(a.x / b, a.y / b);
        }

        public static Vector2 operator /(float a, Vector2 b)
        {
            return b / a;
        }

        public bool Equals(Vector2 other)
        {
            return x == other.x && y == other.y;
        }

        public override bool Equals(object obj)
        {
            if (obj is Vector2 v)
            {
                return Equals(v);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode();
        }

        public override string ToString()
        {
            return $"Vector2({x}, {y})";
        }

        public static bool operator ==(Vector2 a, Vector2 b)
        {
            return a.Equals(b);
        }

        public static bool operator !=(Vector2 a, Vector2 b)
        {
            return !a.Equals(b);
        }

        public static explicit operator Vector2Int(Vector2 a)
        {
            return new Vector2Int((int)a.x, (int)a.y);
        }

        public static implicit operator Vector2(System.Numerics.Vector2 a)
        {
            return new Vector2(a.X, a.Y);
        }

        public static implicit operator System.Numerics.Vector2(Vector2 a)
        {
            return new System.Numerics.Vector2(a.x, a.y);
        }

        public static implicit operator Vector2(OpenTK.Mathematics.Vector2 a)
        {
            return new Vector2(a.X, a.Y);
        }

        public static implicit operator OpenTK.Mathematics.Vector2(Vector2 a)
        {
            return new OpenTK.Mathematics.Vector2(a.x, a.y);
        }

        public Vector2Int RoundToInt()
        {
            return new Vector2Int(Mathf.RoundToInt(x), Mathf.RoundToInt(y));
        }

        public Vector2Int FloorToInt()
        {
            return new Vector2Int(Mathf.FloorToInt(x), Mathf.FloorToInt(y));
        }

        public Vector2Int CeilToInt()
        {
            return new Vector2Int(Mathf.CeilToInt(x), Mathf.CeilToInt(y));
        }
    }
}
